package cn.hetra.hj212;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import cn.hetra.hj212.core.HJ212Codec;
import cn.hetra.hj212.service.HJ212DataHandler;
import cn.hetra.hj212.service.RemotingService;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.LineBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.timeout.IdleStateHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.ObjectProvider;

import java.net.BindException;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class NettyTcpServer{
    private static Logger logger = LoggerFactory.getLogger(NettyTcpServer.class);
    private EventLoopGroup bossGroup;
    private EventLoopGroup ioWorkerGroup;
    private int readerIdle;
    private int writerIdle;
    private int maxFrameLength;
    private int port;
    private RemotingService remotingService;
    ObjectProvider<HJ212DataHandler> hj212DataHandler;
    public NettyTcpServer(int port, int readerIdle, int writerIdle, int maxFrameLength, RemotingService remotingService
                          , ScheduledThreadPoolExecutor scheduledThreadPoolExecutor, ObjectProvider<HJ212DataHandler> hj212DataHandler) {
        this.port = port;
        this.readerIdle = readerIdle;
        this.writerIdle = writerIdle;
        this.maxFrameLength = maxFrameLength;
        this.remotingService = remotingService;
        this.hj212DataHandler = hj212DataHandler;
    }

    public void start() {
        LoggingHandler loggingHandler = new LoggingHandler(LogLevel.WARN);
        this.bossGroup = new NioEventLoopGroup(1,new ThreadFactoryBuilder().setNameFormat("server-boss").setDaemon(true).build());
        this.ioWorkerGroup = new NioEventLoopGroup(0, new ThreadFactoryBuilder().setNameFormat("server-worker").setDaemon(true).build());
        ChannelInitializer channelInitializer = new ChannelInitializer<Channel>() {
            public void initChannel(Channel ch) throws Exception {
                ch.pipeline().addLast(new ChannelHandler[]{new LineBasedFrameDecoder(1024)
                        ,new StringDecoder(),new StringEncoder(),new StringDecoder(),loggingHandler,new HJ212Codec(),new IdleStateHandler(NettyTcpServer.this.readerIdle, NettyTcpServer.this.writerIdle, 0)
                ,new NettyTcpInboundHandler(remotingService,hj212DataHandler)});
            }
        };
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        serverBootstrap.group(this.bossGroup, this.ioWorkerGroup);
        serverBootstrap.channel(NioServerSocketChannel.class);
        serverBootstrap.option(ChannelOption.SO_BACKLOG, 20000);
        serverBootstrap.option(ChannelOption.SO_REUSEADDR, true);
        serverBootstrap.childOption(ChannelOption.TCP_NODELAY, true);
        serverBootstrap.childOption(ChannelOption.SO_KEEPALIVE, false);
        serverBootstrap.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
        serverBootstrap.childOption(ChannelOption.RCVBUF_ALLOCATOR, AdaptiveRecvByteBufAllocator.DEFAULT);
        serverBootstrap.childHandler(channelInitializer);
            ChannelFuture channelFuture = serverBootstrap.bind(port);
            channelFuture.awaitUninterruptibly();
            if (channelFuture.isSuccess()) {
                logger.info("Netty server started at {}.", port);//
                this.ioWorkerGroup.next().scheduleWithFixedDelay(new Runnable() {
                    public void run() {
                        NettyTcpServer.logger.info("Netty occupied direct memory {}KB", PooledByteBufAllocator.DEFAULT.metric().usedDirectMemory() / 1024L);
                    }
                }, 60L, 60L, TimeUnit.SECONDS);
                return;
            }

            Throwable cause = channelFuture.cause();// 90
            if (!(cause instanceof BindException)) {// 91
                throw new IllegalStateException("Failed to start netty server at " + this.port, cause);
            }
            logger.warn("Failed to start netty server at {},will try other port.", this.port, cause);
    }

    public int getPort() {
        return this.port;// 108
    }

    public void stop() {
        if (this.bossGroup != null) {
            this.bossGroup.shutdownGracefully();
        }

        if (this.ioWorkerGroup != null) {
            this.ioWorkerGroup.shutdownGracefully();
        }

    }
}
